﻿using System;
using System.Linq;
using System.Xml;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;
using Notoric.Model.Validation;
using Notoric.Model.Collections;

namespace Notoric.Model.Elements
{
    /// <summary>
    /// Implements the Project model element.
    /// </summary>
    public class ProjectElement : ModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="ProjectElement"/> instance and sets its fields to default values.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="ProjectPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="packagePart"/> is <see langword="null"/>.
        /// </exception>
        internal ProjectElement(ProjectPart packagePart)
            : base(packagePart)
        {
            scores = new PathCollection(scoresElement);
            resources = new PathCollection(resourcesElement);
            compositions = new PathCollection(compositionsElement);
        }

        /// <summary>
        /// Creates a new <see cref="ProjectElement"/> instance and loads its fields from the given element.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="ProjectPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The element that represents this object
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="packagePart"/> or <paramref name="element"/> is <see langword="null"/>.
        /// </exception>
        internal ProjectElement(ProjectPart packagePart, XmlElement element)
            : base(packagePart, element)
        {
            nameAttribute = element.Attributes[PackageVocabulary.NameAttribute];
            resourcesElement = element[PackageVocabulary.ResourceElementName + "s", PackageVocabulary.PackageNamespaceUri];
            scoresElement = element[PackageVocabulary.ScoreElementName + "s", PackageVocabulary.PackageNamespaceUri];
            compositionsElement = element[PackageVocabulary.CompositionElementName + "s", PackageVocabulary.PackageNamespaceUri];

            scores = new PathCollection(scoresElement);
            resources = new PathCollection(resourcesElement);
            compositions = new PathCollection(compositionsElement);
        }

        #endregion

        #region ModelElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateObjectElement(XmlDocument document)
        {
            var element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.ProjectElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            nameAttribute = document.CreateAttribute(PackageVocabulary.NameAttribute);
            element.Attributes.Append(nameAttribute);

            scoresElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.ScoreElementName + "s",
                PackageVocabulary.PackageNamespaceUri
                );
            element.AppendChild(scoresElement);

            resourcesElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.ResourceElementName + "s",
                PackageVocabulary.PackageNamespaceUri
                );
            element.AppendChild(resourcesElement);

            compositionsElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.CompositionElementName + "s",
                PackageVocabulary.PackageNamespaceUri
                );
            element.AppendChild(compositionsElement);
            
            return element;
        }

        /// <inheritdoc/>
        protected override Rule[] CreateValidationRules(string propertyName)
        {
            if (propertyName == "Name")
            {
                var nameRules = new Rule[1];
                nameRules[0] = new NotNullRule<string>(
                    "Name",
                    "The Name value must be a string different from null!",
                    "ValidationRules.Elements.Project.Name.NotNull"
                    );

                return nameRules;
            }

            return null;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the collection of compositions in this project.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// Each item is a path to a composition in this project.
        /// The path begins with a slash ('/') and is relative to the root compositions directory.
        /// <example>
        /// Path: <i>/Bach/Organ/Art of Fugue</i><br/>
        /// represents a composition named "Art of Fugue" that is in the [Root Compositions Directory]/Bach/Organ directory.
        /// </example>
        /// </para>
        /// </value>
        public PathCollection Compositions
        {
            get { return compositions; }
        }

        /// <summary>
        /// Gets or sets the name of the project.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// Name of the project.
        /// </para>
        /// </value>
        public string Name
        {
            get { return nameAttribute.Value; }
            set
            {
                ValidateAndThrow("Name", value);

                nameAttribute.Value = value;
                RaisePropertyChanged("Name");
            }
        }

        /// <summary>
        /// Gets the collection of resources in this project.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// Each item is a path to a resource in this project.
        /// The path begins with a slash ('/') and is relative to the root resources directory.
        /// <example>
        /// Path: <i>/Pictures/Authors/Bach.jpg</i><br/>
        /// represents an image (resource) named "Bach.jpg" that is in the [Root Resources Directory]/Pictures/Authors.
        /// </example>
        /// </para>
        /// </value>
        public PathCollection Resources
        {
            get { return resources; }
        }

        /// <summary>
        /// Gets the collection of scores in this project.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// Each item is a path to a score in this project.
        /// The path begins with a slash ('/') and is relative to the root scores directory.
        /// <example>
        /// Path: <i>/Bach/Organ/Art of Fugue</i><br/>
        /// represents a score named "Art of Fugue" that is in the [Root Scores Directory]/Bach/Organ directory.
        /// </example>
        /// </para>
        /// </value>
        public PathCollection Scores
        {
            get { return scores; }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the collection of compositions in this project.
        /// </summary>
        private PathCollection compositions;
        /// <summary>
        /// Holds a reference to the compositions child element of the object element.
        /// </summary>
        private XmlElement compositionsElement;
        /// <summary>
        /// Holds a reference to the name attribute of the object element.
        /// </summary>
        private XmlAttribute nameAttribute;
        /// <summary>
        /// Holds the collection of resources in this project.
        /// </summary>
        private PathCollection resources;
        /// <summary>
        /// Holds a reference to the resources child element of the object element.
        /// </summary>
        private XmlElement resourcesElement;
        /// <summary>
        /// Holds the collection of scores in this project.
        /// </summary>
        private PathCollection scores;
        /// <summary>
        /// Holds a reference to the scores child element of the object element.
        /// </summary>
        private XmlElement scoresElement;

        #endregion
    }
}
